package main

import (
	"database/sql"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"sort"
)

type editorTreeNode struct {
	ID       uint32            `json:"id,omitempty"`
	Label    string            `json:"label,omitempty"`
	Children []*editorTreeNode `json:"children,omitempty"`
}

func handleGetEditorTreeView(w http.ResponseWriter, r *http.Request, Type int,
	funcGetAllItemNames func(*sql.DB) (map[uint32]string, error)) {
	treeData, err := getTreeView(worldDB, Type)
	if err != nil && err != sql.ErrNoRows {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if treeData == nil {
		treeData = []byte("[]")
	}
	var treeNodes []*editorTreeNode
	if err = json.Unmarshal(treeData, &treeNodes); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	itemNames, err := funcGetAllItemNames(worldDB)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	treeNodes = combineEditorTreeNode(treeNodes, itemNames)
	if treeData, err = json.Marshal(treeNodes); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJsonResponseData(w, treeData)
}

func handleSaveEditorTreeView(w http.ResponseWriter, r *http.Request, Type int) {
	treeData, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var treeNodes []*editorTreeNode
	if err = json.Unmarshal(treeData, &treeNodes); err != nil {
		http.Error(w, err.Error(), http.StatusNotAcceptable)
		return
	}
	treeNodes = cleanEditorTreeNode(treeNodes)
	if treeData, err = json.Marshal(treeNodes); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = setTreeView(worldDB, Type, treeData)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeOKResponseData(w)
}

func combineEditorTreeNode(
	treeNodes []*editorTreeNode, itemNames map[uint32]string) []*editorTreeNode {
	var combineTreeNodes func([]*editorTreeNode) []*editorTreeNode
	combineTreeNodes = func(treeNodes []*editorTreeNode) []*editorTreeNode {
		if treeNodes != nil {
			var n int
			for i, treeNode := range treeNodes {
				if treeNode.Children != nil {
					treeNode.Children = combineTreeNodes(treeNode.Children)
				}
				if treeNode.ID != 0 {
					if itemName, isOK := itemNames[treeNode.ID]; isOK {
						treeNode.Label = itemName
						delete(itemNames, treeNode.ID)
					} else {
						continue
					}
				}
				if i != n {
					treeNodes[n] = treeNode
				}
				n++
			}
			return treeNodes[:n]
		}
		return nil
	}
	treeNodes = combineTreeNodes(treeNodes)
	var itemIDs []uint32
	for itemID := range itemNames {
		itemIDs = append(itemIDs, itemID)
	}
	sort.Slice(itemIDs, func(i, j int) bool {
		return itemIDs[i] < itemIDs[j]
	})
	for _, itemID := range itemIDs {
		treeNodes = append(treeNodes,
			&editorTreeNode{ID: itemID, Label: itemNames[itemID]})
	}
	return treeNodes
}

func cleanEditorTreeNode(treeNodes []*editorTreeNode) []*editorTreeNode {
	var cleanTreeNodes func([]*editorTreeNode)
	cleanTreeNodes = func(treeNodes []*editorTreeNode) {
		if treeNodes != nil {
			for _, treeNode := range treeNodes {
				if treeNode.Children != nil {
					cleanTreeNodes(treeNode.Children)
				}
				if treeNode.ID != 0 {
					treeNode.Label = ""
				}
			}
		}
	}
	cleanTreeNodes(treeNodes)
	return treeNodes
}

type editorConstTreeNode struct {
	Cls      int                    `json:"cls,omitempty"`
	ID       uint32                 `json:"id,omitempty"`
	Label    string                 `json:"label,omitempty"`
	Children []*editorConstTreeNode `json:"children,omitempty"`
}

func handleGetEditorConstTreeView(w http.ResponseWriter, r *http.Request, Type int,
	options []webCascaderOption,
	funcGetAllItemNames func(*sql.DB) (map[uint32]string, error),
	funcGetAllItemClasses func(*sql.DB) (map[uint32][]int, error)) {
	treeData, err := getTreeView(worldDB, Type)
	if err != nil && err != sql.ErrNoRows {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if treeData == nil {
		treeData = []byte("null")
	}
	var itemIDs []uint32
	if err = json.Unmarshal(treeData, &itemIDs); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	itemNames, err := funcGetAllItemNames(worldDB)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	itemClasses, err := funcGetAllItemClasses(worldDB)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	treeNodes := combineEditorConstTreeNode(options, itemIDs, itemNames, itemClasses)
	if treeData, err = json.Marshal(treeNodes); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJsonResponseData(w, treeData)
}

func handleSaveEditorConstTreeView(w http.ResponseWriter, r *http.Request, Type int) {
	treeData, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var itemIDs []uint32
	if err = json.Unmarshal(treeData, &itemIDs); err != nil {
		http.Error(w, err.Error(), http.StatusNotAcceptable)
		return
	}
	if treeData, err = json.Marshal(itemIDs); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	err = setTreeView(worldDB, Type, treeData)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeOKResponseData(w)
}

func combineEditorConstTreeNode(options []webCascaderOption, itemIDs []uint32,
	itemNames map[uint32]string, itemClasses map[uint32][]int) []*editorConstTreeNode {
	var options2TreeNodes func(options []webCascaderOption) []*editorConstTreeNode
	options2TreeNodes = func(options []webCascaderOption) []*editorConstTreeNode {
		var treeNodes []*editorConstTreeNode
		for _, option := range options {
			treeNodes = append(treeNodes, &editorConstTreeNode{
				option.Value, 0, option.Label, options2TreeNodes(option.Children)})
		}
		return treeNodes
	}
	findTreeNodeOfSlice := func(
		treeNodesPtr *[]*editorConstTreeNode, itemClass []int) *[]*editorConstTreeNode {
		for _, cls := range itemClass {
			var isFound bool
			for _, treeNode := range *treeNodesPtr {
				if treeNode.ID == 0 && treeNode.Cls == cls {
					treeNodesPtr = &treeNode.Children
					isFound = true
					break
				}
			}
			if !isFound {
				break
			}
		}
		return treeNodesPtr
	}
	treeNodes := options2TreeNodes(options)
	for _, itemID := range itemIDs {
		itemClass, isOK := itemClasses[itemID]
		if !isOK {
			continue
		}
		treeNodesPtr := findTreeNodeOfSlice(&treeNodes, itemClass)
		*treeNodesPtr = append(*treeNodesPtr,
			&editorConstTreeNode{ID: itemID, Label: itemNames[itemID]})
		delete(itemClasses, itemID)
	}
	for itemID, itemClass := range itemClasses {
		treeNodesPtr := findTreeNodeOfSlice(&treeNodes, itemClass)
		*treeNodesPtr = append(*treeNodesPtr,
			&editorConstTreeNode{ID: itemID, Label: itemNames[itemID]})
	}
	return treeNodes
}
